package com.zhou.base.components.bs.common.util.pwd;

import java.io.IOException;
import java.nio.Buffer;
import java.nio.CharBuffer;
import java.security.SecureRandom;
import java.util.List;
import java.util.Random;

/**
 * @author xianfeng
 * @date 2023/9/20 17:20
 */
public class PasswordGenerator {

    private final Random random;

    public PasswordGenerator() {
        this(new SecureRandom());
    }

    public PasswordGenerator(final Random r) {
        random = r;
    }

    public String generatePassword(final int length, final List<CharacterRule> rules) {
        if (length <= 0) {
            throw new IllegalArgumentException("length must be greater than 0");
        }

        final StringBuilder allChars = new StringBuilder();

        final CharBuffer buffer = CharBuffer.allocate(length);
        if (rules != null) {
            for (CharacterRule rule : rules) {
                fillRandomCharacters(
                        rule.getValidCharacters(),
                        length <= rule.getNumberOfCharacters() ? length : rule.getNumberOfCharacters(),
                        buffer);
                allChars.append(rule.getValidCharacters());
            }
        }
        fillRandomCharacters(allChars, length - buffer.position(), buffer);
        // cast to Buffer prevents NoSuchMethodError when compiled on JDK9+ and run on JDK8
        ((Buffer) buffer).flip();
        randomize(buffer);
        return buffer.toString();
    }

    protected void fillRandomCharacters(final CharSequence source, final int count, final Appendable target) {
        for (int i = 0; i < count; i++) {
            try {
                target.append(source.charAt(random.nextInt(source.length())));
            } catch (IOException e) {
                throw new RuntimeException("Error appending characters.", e);
            }
        }
    }

    protected void randomize(final CharBuffer buffer) {
        char c;
        int n;
        for (int i = buffer.position(); i < buffer.limit(); i++) {
            n = random.nextInt(buffer.length());
            c = buffer.get(n);
            buffer.put(n, buffer.get(i));
            buffer.put(i, c);
        }
    }

}
